/***************************************************************************
 * 
 * 海尔语音ota app
 * 
 * ota service and gateway app watch dog
 * 
 * @author nicholas.yu
 * @date 2020-09-08
 *
 ***************************************************************************/

#include "stdio.h"
#include "string.h"
#include <fstream>
#include <algorithm>
#include <sys/socket.h>
#include <sys/sysinfo.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <sys/vfs.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#include "sdk.h"
#include "log.h"
#include "json/CJsonObject.hpp"

using namespace gmtlib;
using namespace gmtiot;

static string TAG = {"MAIN"};

class app_cb : public virtual app_callback
{
public:
    /**
     * app连接云状态变化回调
     *
     * @param state：CloudState.DeviceStateOnline=连云成功, CloudState.DeviceStateOffline=连云失败
     */
    void onCloudStateChanged(CloudState state)
    {
        string status = (state == CloudStateOnline) ? "online" : "offline";
        Log::d(TAG, "onCloudStateChanged: {0}", status);
    }

    /**
     * 用户账号下绑定设备列表更新
     *
     * @param devices：新的绑定设备列表
     */
    void onDeviceListChanged(list<DeviceInfo> &devices)
    {
        neb::CJsonObject json;
        for (DeviceInfo device : devices)
        {
            neb::CJsonObject dev;
            dev.Add("deviceId", device.deviceId);
            dev.Add("productName", device.productName);
            dev.Add("brandName", device.brandName);
            dev.Add("modelName", device.modelName);
            dev.Add("typeName", device.typeName);
            dev.Add("onOffStatus", device.onOffStatus);
            json.Add(dev);
        }
        Log::d(TAG, "onDeviceAttributeChanged: {0}", json.ToString());
    }

    /**
     * 设备连云状态变化
     *
     * @param deviceId：设备ID
     * @param state：        CloudState.DeviceStateOnline=连云成功, CloudState.DeviceStateOffline=连云失败
     */
    void onDeviceCloudStateChanged(const string &deviceId, CloudState state)
    {
        string status = (state == CloudStateOnline) ? "online" : "offline";
        Log::d(TAG, "onDeviceCloudStateChanged: {0}, {1}", deviceId, status);
    }

    /**
     * 设备属性值变化
     *
     * @param deviceId：设备ID
     * @param attributes：   设备最新的属性值列表
     */
    void onDeviceAttributeChanged(const string &deviceId, list<AttributeValue> &attributes)
    {
        neb::CJsonObject json;
        for (AttributeValue attribute : attributes)
        {
            neb::CJsonObject attr;
            attr.Add("name", attribute.name);
            attr.Add("value", attribute.value);
            json.Add(attr);
        }
        Log::d(TAG, "onDeviceAttributeChanged: {0}, {1}", deviceId, json.ToString());
    }

    /**
     * 订阅设备结果回调
     *
     * @param result：  true=订阅成功，false=订阅失败
     * @param deviceId
     */
    void onSubscribeResult(bool result, const string &deviceId)
    {
        Log::d(TAG, "onSubscribeResult: {0}, {1}", deviceId, std::to_string(result));
    }

    /**
     * 发布信息结果回调
     *
     * @param result：  true=订阅成功，false=订阅失败
     * @param deviceId
     */
    void onPublishResult(bool result, const string &deviceId)
    {
        Log::d(TAG, "onSubscribeResult: {0}, {1}", deviceId, std::to_string(result));
    }

    /**
     * login 回调
     *
     * @param result：  true=订阅成功，false=订阅失败
     * @param userToken:
    */
    void onLoginResult(bool result, const string &userToken, const string &validEndTime)
    {
        Log::d(TAG, "onLoginResult: {0}, {1}, {2}", std::to_string(result), userToken, validEndTime);
    }

    /**
     * bind device 回调
     *
     * @param result：  true=订阅成功，false=订阅失败
     * @param userToken: 
     */
    virtual void onBindDeviceResult(bool result, const DeviceInfo &deviceInfo)
    {
        Log::d(TAG, "onBindDeviceResult: {0}, {1}, {2}", std::to_string(result), deviceInfo.deviceId, deviceInfo.productName);
    }
};

class dev_cb : public virtual dev_callback
{

public:
    dev_cb() = default;
    dev_cb(const string &deviceId) { this->deviceId = deviceId; }

    /**
     * 设备连云状态变化回调
     *
     * @param state：    DeviceCloudState.DeviceStateOnline=连云成功，DeviceCloudState.DeviceStateOffline=连云失败
     */
    void onDeviceCloudStateChanged(CloudState state)
    {
        string status = (state == CloudStateOnline) ? "online" : "offline";
        Log::d(TAG, "onDeviceCloudStateChanged: {0}, {1}, {2}", deviceId, status);
    }

    /**
     * 读取设备参数回调，设备需要在Read回调里面读取最新参数并调用response回复Read结果
     *
     * @param attributes： 属性名字列表
     * @param msgId：      消息ID，用于操作同步
     */
    void onDeviceRead(list<AttributeValue> &attributes, const string &msgId)
    {
        neb::CJsonObject json;
        for (AttributeValue attribute : attributes)
        {
            neb::CJsonObject attr;
            attr.Add("name", attribute.name);
            attr.Add("value", attribute.value);
            json.Add(attr);
        }
        Log::d(TAG, "onDeviceRead: {0}, {1}", msgId, json.ToString());
    }

    /**
     * 写入设备参数回调，设备需要在Write回调里面写入最新参数并调用response回复Write结果
     *
     * @param attributeValues： 属性键值对
     * @param msgId：      消息ID，用于操作同步
     */
    void onDeviceWrite(list<AttributeValue> &attributes, const string &msgId)
    {
        neb::CJsonObject json;
        for (AttributeValue attribute : attributes)
        {
            neb::CJsonObject attr;
            attr.Add("name", attribute.name);
            attr.Add("value", attribute.value);
            json.Add(attr);
        }
        Log::d(TAG, "onDeviceWrite: {0}, {1}", msgId, json.ToString());
    }

    /**
     * 设备升级回调
     *
     * @param newVersions 升级包信息
     */
    virtual void onNewVersion(list<NewVersionInfo> &newVersions)
    {
        neb::CJsonObject json;
        for (NewVersionInfo versionInfo : newVersions)
        {
            neb::CJsonObject info;
            info.Add("packageName", versionInfo.packageName);
            info.Add("typeId", versionInfo.typeId);
            info.Add("modelCode", versionInfo.modelCode);
            info.Add("type", versionInfo.type);
            info.Add("platform", versionInfo.platform);
            info.Add("silentFlag", versionInfo.silentFlag);

            neb::CJsonObject fullFile;
            fullFile.Add("versionName", versionInfo.fullFile.versionName);
            fullFile.Add("versionCode", versionInfo.fullFile.versionCode);
            fullFile.Add("fileUrl", versionInfo.fullFile.fileUrl);
            fullFile.Add("fileSize", versionInfo.fullFile.fileSize);
            fullFile.Add("checkSum", versionInfo.fullFile.checkSum);

            neb::CJsonObject diffFile;
            diffFile.Add("versionName", versionInfo.diffFile.versionName);
            diffFile.Add("versionCode", versionInfo.diffFile.versionCode);
            diffFile.Add("oldVersionName", versionInfo.diffFile.oldVersionName);
            diffFile.Add("oldVersionCode", versionInfo.diffFile.oldVersionCode);
            diffFile.Add("fileUrl", versionInfo.diffFile.fileUrl);
            diffFile.Add("fileSize", versionInfo.diffFile.fileSize);
            diffFile.Add("checkSum", versionInfo.diffFile.checkSum);

            info.Add("fullFile", fullFile);
            info.Add("diffFile", diffFile);
            json.Add(info);
        }
        Log::d(TAG, "onNewVersion: {0}", json.ToString());
    }

private:
    string deviceId;
};

/**
 * @brief network config callback 
 */
class config_cb : public virtual config_callback
{

public:
    config_cb() = default;
    ~config_cb() {}

    /**
     * @brief config process callback
     * @param step 
     */
    virtual void onConfigProcess(ConfigNetworkStep step)
    {
        Log::d(TAG, "onConfigProcess: {0}", step);
    }

    /**
     * @brief start WiFi AP mode 
     * 
     * @param apname ap name
     */
    virtual bool onConfigAP(const string &apname)
    {
        Log::d(TAG, "onConfigAP: {0}", apname);
        return true;
    }

    /**
     * @brief start WiFi station mode
     * 
     * @param ssid WiFi ssid 
     * @param pwd WiFi password
     * @param bssid WiFi bssid
     */
    virtual bool onConfigSTA(const string &ssid, const string &pwd, const string &bssid)
    {
        Log::d(TAG, "onConfigSTA: {0}, {1}, {2}", ssid, pwd, bssid);
        return true;
    }

    /**
     * @brief start BLE broadcast and waiting for ssid&password transfered by BLE
     * 
     * @param broadcastName BLE device broadcast name
     */
    virtual bool onConfigBLE(const string &broadcastName)
    {
        Log::d(TAG, "onConfigBLE: {0}", broadcastName);
        return true;
    }
};

/**
 * @brief Get the Ifname of wlan
 * 
 * @return const std::string 
 */
const std::string getIfname()
{
    string ifname;
    // read first ifname
    struct if_nameindex *head, *ptr;
    ptr = head = if_nameindex();
    if (head != NULL)
    {
        while (ptr->if_name != NULL)
        {
            ifname = ptr->if_name;
            if (ifname != "lo")
            {
                break;
            }
            ptr++;
        }

        if_freenameindex(head);
    }
    return ifname;
}

/**
 * @brief 获取wlan MAC地址
 * @return
 */
const std::string getMac(bool lowercase)
{
    std::string mac = "222222222222";

    int sock = 0x00;
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        return mac;
    }

    struct ifreq mifreq;
    strcpy(mifreq.ifr_name, getIfname().c_str());

    /*获取MAC*/
    if (ioctl(sock, SIOCGIFHWADDR, &mifreq) < 0)
    {
        close(sock);
        return mac;
    }

    /*将MAC地址进行赋值*/
    char macbuf[13];
    char *ptr = mifreq.ifr_hwaddr.sa_data;
    memset(macbuf, 0, 13);
    for (int i = 0; i < 6; i++)
    {
        snprintf(macbuf + i * 2, 3, "%02x", (ptr[i] & 0xff));
    }
    close(sock);
    mac = macbuf;
    std::string bmac = mac;
    transform(bmac.begin(), bmac.end(), bmac.begin(), ::toupper);

    if (lowercase)
        return mac;
    else
        return bmac;
}

/**
 * 程序入口函数
 **/
int main(int argc, char *argv[])
{
    int ret = 0;

    const std::string mac = getMac(true);
    Log::setLogLevel(Log::LogLevelDebug);

    app_callback_ptr app_ptr = std::shared_ptr<app_callback>(new app_cb());
    dev_callback_ptr dev_ptr = std::shared_ptr<dev_callback>(new dev_cb(mac));
    list<PackageVersion> versions;
    PackageVersion versionApp;
    versionApp.packageName = "com.gmt.demo.app";
    versionApp.platform = "rk3308";
    versionApp.versionCode = 100;
    versionApp.versionName = "1.00";
    PackageVersion versionRom;
    versionRom.packageName = "com.gmt.demo.rom";
    versionRom.platform = "rk3308";
    versionRom.versionCode = 100;
    versionRom.versionName = "1.00";
    versions.push_back(versionApp);
    versions.push_back(versionRom);

    Log::d(TAG, "==============================================================");
    Log::d(TAG, "== IOTDEMO APP Start...");
    Log::d(TAG, "==============================================================");

    // init sdk
    sdk::get()
        .clientId("9199aab73cb442ce8dd04865d3a3ddfd")
        .clientSecret("ec5a625338744cea98597ad860736d5a")
        .debugFlag(true)
        .filePath("/home/nicholas/Documents/")
        .init();

    // device connect test
    sdk::get()
        .deviceId(mac)
        .typeId("9F82DEFA2798460EA1F14262A824C664")
        .modelCode("FF6608C754CF446B8AE80636E8355BA0")
        .position(121.23442, 32.23234)
        .otaVersion(versions)
        .deviceConnect(dev_ptr);

    // app connect test
    sdk::get()
        .userToken("847ccfa9468c444ab3fd27b0252f4d0a")
        .appConnect(app_ptr);

    // test login
    //    sdk::get().loginRequestCode("18917034525");
    //    sdk::get().loginByMsgCode("18917034525","758058");
    //    sdk::get().updateToken("847ccfa9468c444ab3fd27b0252f4d0a");

    while (1)
    {
        usleep(1000000);
    }
    return ret;
}